package com.zyj.core.sqlsource;

import com.zyj.core.parser.SqlSourceParser;
import com.zyj.core.sqlnode.DynamicContext;
import com.zyj.core.sqlnode.SqlNode;

/**
 * 用于封装和解析带有${}或者动态标签的一些SQL信息
 * SELECT * FROM user WHERE name = ${name}
 * 每一次根据参数的不同得到的sql不同
 * SELECT * FROM user WHERE name = zhangsan
 * SELECT * FROM user WHERE name = lisi
 * <p>
 * 解析时机：
 * 1、RawSqlSource构造的时候进行SQL解析（只会解析一次）
 * 2、DynamicSqlSource每次调用getBoundSql的时候才会解析
 */
public class DynamicSqlSource implements SqlSource {

    /**
     * 与RawSqlSource不同的是，DynamicSqlSource并不直接持有staticSqlSource对象，而是持有一个mixedSqlNode，一开始的mixedSqlNode中的sql并没有解析
     * <p>
     * 在调用getBoundSql方法的时候，才根据mixedSqlNode中的信息完成sql解析，得到解析完成的sql，再封装成staticSqlSource对象
     */
    private SqlNode rootSqlNode;

    /**
     * 传入的mixedSqlNode是已经根据配置文件中的标签解析完成，封装好的SqlNode对象
     *
     * @param rootSqlNode
     */
    public DynamicSqlSource(SqlNode rootSqlNode) {
        this.rootSqlNode = rootSqlNode;
    }

    /**
     * 每一次都需要重新解析sql语句
     *
     * @param param 为了解析${}需要的由客户端传入的参数，例如如下map
     *              Map map = new HashMap();
     *              map.put("username", "千年老亚瑟");
     *              map.put("sex", "男");
     * @return
     */
    @Override
    public BoundSql getBoundSql(Object param) {
        // 1.解析SqlNode中的所有节点信息，最终会组成一条SQL语句(解析${}或者动态标签)
        DynamicContext context = new DynamicContext(param);

        // 调用SqlNode的apply方法完成了sql片段的拼接，但是此时还没有解析sql中的#{}
        // 此时sql为：SELECT * FROM user WHERE sex = #{sex} AND username like '%千年老亚瑟' AND username like '%千年老亚瑟' AND 1=1
        rootSqlNode.apply(context);

        // 将动态sql标签以及${}解析完成之后，在最后再去解析#{}
        // 经过以上解析之后sql为：
        // SELECT * FROM user WHERE sex = #{sex} AND username like '%千年老亚瑟' AND username like '%千年老亚瑟' AND 1=1
        // 此时${}和动态标签都已经解析完毕，仅剩下#{}还没有解析
        // 此时的sql再去解析#{}的处理方式与RawSqlSource解析#{}的步骤是一样的
        // 2.解析#{}
        SqlSourceParser sqlSourceParser = new SqlSourceParser();

        // #{}解析完毕之后
        // SELECT * FROM user WHERE sex = ? AND username like '%千年老亚瑟' AND username like '%千年老亚瑟' AND 1=1

        /**
         * 调用getBoundSql时才在此处根据持有的由XMLScriptBuilder解析出来的mixedSqlNode对象来完成sql语句的解析工作，得到解析完成的sql语句，并由staticSqlSource对象来封装
         */
        // 此处返回的是 StaticSqlSource
        SqlSource sqlSource = sqlSourceParser.parseSqlSource(context.getSql());

        // 此处调的是StaticSqlSource中实现的getBoundSql方法，而不再是DynamicSqlSource中的了
        // 此时sqlSource中持有的sql为：SELECT * FROM user WHERE sex = ? AND username like '%千年老亚瑟' AND username like '%千年老亚瑟' AND 1=1
        return sqlSource.getBoundSql(param);
    }
}
